/**
 * 20211123 created by jordia
 * 20211129：add method [extractTransform]
 */
import {
    Object3D,
    CatmullRomCurve3,
    Vector3,
    BufferGeometry,
    BufferAttribute,
    Line,
    LineBasicMaterial,
    Color,
    Quaternion, Matrix4
} from "three";

const REF_X=new Vector3(1,0,0);

/**
 * 曲线动画对象
 * @param object {object3D} 所对应的3D对象
 * @param positionT {number} 在曲线上的位置[0-1]
 * @param speedT {number} 沿曲线移动的速度[0-1]
 * @param offsetU {number} 切线平面横向偏移
 * @param offsetV {number} 切线平面纵向偏移
 *
 * @property object
 * @property positionT
 * @property speedT
 * @property offsetU
 * @property offsetV
 * @property previousT {number} 上一个t值(positionT为当前t值)
 * @property onStep {function} 步进回调函数(previousT:前一个位置t,currentT:当前位置t)=>{}
 * @property onSynchronize {function} 同步回调函数(this:当前动画对象)=>{}
 */
class AnimateObject{
    constructor(object,positionT,speedT,offsetU,offsetV){
        this._object=object;
        this._positionT=positionT;
        this._speedT=speedT;
        this._tV0=new Vector3();
        this._tV1=new Vector3();
        this._tV2=new Vector3();
        this._offsetU=offsetU;
        this._offsetV=offsetV;
        this._tQ0=new Quaternion();
        this._tQ1=new Quaternion();
        this._tM0=new Matrix4();
        this._tM1=new Matrix4();
        this._stepCallback=null;
        this._syncCallback=null;
        this._preT=this._positionT;
    }

    //.....................................................public.......................................................

    get object(){
        return this._object;
    }
    get positionT(){
        return this._positionT;
    }
    set positionT(num){
        this._positionT=Math.max(Math.min(num,1),0);
    }
    get previousT(){
        return this._preT;
    }
    get speedT(){
        return this._speedT;
    }
    set speedT(num){
        this._speedT=Math.max(Math.min(num,1),0);
    }
    get offsetU(){
        return this._offsetU;
    }
    set offsetU(num){
        this._offsetU=num;
    }
    get offsetV(){
        return this._offsetV;
    }
    set offsetV(num){
        this._offsetV=num;
    }
    set onStep(func_previousT_currentT){
        this._stepCallback=func_previousT_currentT;
    }
    set onSynchronize(func_this){
        this._syncCallback=func_this;
    }

    //.....................................................private......................................................

    _stepIn(t){
        this._preT=this._positionT;
        this._positionT+=this._speedT*t;
        if(this._positionT>1)this._positionT-=1;
        if(this._positionT<0)this._positionT+=1;
        if(this._stepCallback)this._stepCallback(this._preT,this._positionT);
    }
    _synchronize(curve,container){
        let pos=this._tV0;
        let tan=this._tV1;
        let ofs=this._tV2;
        let rot=this._tQ0;
        curve.getPointAt(this._positionT,pos);
        curve.getTangentAt(this._positionT,tan);
        rot.setFromUnitVectors(REF_X,tan);
        ofs.set(0,this._offsetV,this._offsetU).applyQuaternion(rot);
        pos.add(ofs).applyMatrix4(container.matrixWorld);
        rot.premultiply(this._tQ1.setFromRotationMatrix(this._tM0.extractRotation(container.matrixWorld)));
        if(this._object.parent){
            let inv=this._tM1.copy(this._object.parent.matrixWorld).invert();
            pos.applyMatrix4(inv);
            rot.premultiply(this._tQ1.setFromRotationMatrix(this._tM0.extractRotation(inv)));
        }
        this._object.position.copy(pos);
        this._object.quaternion.copy(rot);
        if(this._syncCallback)this._syncCallback(this);
    }
}

/**
 * 沿曲线运动的动画
 * @param points {array} 用于生成曲线的关键点序列
 * @param values {object} 可选参数对象：
 *      {
 *          closed:false, {boolean} 曲线是否闭合
 *          curveType:'catmullrom', {string} 曲线类型（可选值:'centripetal','chordal','catmullrom'）
 *          tension:0, {number} 曲线的紧张度（值越大，越圆滑）
 *      }
 *
 * @property debug {*|object} 绘制调试图形（false/null/undefined将关闭调试）
 *      {
 *          segments:50, {number} 调试图形的段数
 *          color:new Color('#ff00f9'), {color} 调试图形的颜色
 *      }
 * @property path {CatmullRomCurve3} 曲线对象
 */
export default class AnimatePath extends Object3D{
    constructor(points,values={}){
        super();
        this._init(points,values);
    }

    //...............................................public..............................................

    set debug(info){
        if(info){
            this.debug=false;
            let len=(typeof info!=='object')?50:(info.segments||50);
            let ps=new Float32Array((len+1)*3);
            let inv=1/len;
            let v=this._tV0;
            for(let i=0;i<=len;i++){
                this._path.getPoint(inv*i,v);
                ps[i*3]=v.x;
                ps[i*3+1]=v.y;
                ps[i*3+2]=v.z;
            }
            let g=new BufferGeometry();
            g.setAttribute('position',new BufferAttribute(ps,3));
            this._debug=new Line(g,new LineBasicMaterial({
                color:(typeof info!=='object')?new Color('#ff00f9'):(info.color||new Color('#ff00f9')),
            }));
            this.add(this._debug);
        }else{
            if(!this._debug)return;
            this.remove(this._debug);
            this._debug.geometry.dispose();
            this._debug.material.dispose();
        }
    }
    get path(){
        return this._path;
    }

    /**
     * 获取[T,U,V]的相应位置及旋转
     * @param posT
     * @param uOffset
     * @param vOffset
     * @param vecOut {Vector3} 输出位置
     * @param quatOut {Quaternion} 输出旋转
     */
    extractTransform(posT,uOffset,vOffset,vecOut,quatOut){
        let curve=this._path;
        let pos=this._tV0;
        let tan=this._tV1;
        let ofs=this._tV2;
        let rot=this._tQ0;
        curve.getPointAt(posT,pos);
        curve.getTangentAt(posT,tan);
        rot.setFromUnitVectors(REF_X,tan);
        ofs.set(0,vOffset,uOffset).applyQuaternion(rot);
        pos.add(ofs).applyMatrix4(this.matrixWorld);
        rot.premultiply(this._tQ1.setFromRotationMatrix(this._tM0.extractRotation(this.matrixWorld)));
        if(vecOut)vecOut.copy(pos);
        if(quatOut)quatOut.copy(rot);
    }

    /**
     * 添加动画对象
     * @param object {Object3D} 要应用动画的3D对象
     * @param posT {number} 在曲线上的初始位置t[0-1]
     * @param speedT {number} 运动速度（+为从头到尾，-则从尾到头）
     * @param uOffset {number} 切线横向偏移值
     * @param vOffset {number} 切线纵向偏移值
     * @returns {null|AnimateObject} 生成的动画对象
     */
    addTarget(object,posT,speedT,uOffset,vOffset){
        for(let i=0;i<this._targets.length;i++){
            if(this._targets[i].object.uuid===object.uuid)return null;
        }
        let ao=new AnimateObject(object,posT,speedT,uOffset,vOffset);
        object.userData.animateObject=ao;
        this._targets.push(ao);
        return ao;
    }

    /**
     * 移除动画对象
     * @param object {Object3D} 动画对象相应的3D对象
     */
    removeTarget(object){
        let idx=-1;
        for(let i=0;i<this._targets.length;i++){
            if(this._targets[i].object.uuid!==object.uuid)continue;
            idx=i;
            break;
        }
        if(idx<0)return;
        object.userData.animateObject=undefined;
        this._targets.splice(idx,1);
    }

    /**
     * 清除动画对象
     */
    clearTargets(){
        for(let i=this._targets.length;i>=0;i--){
            let obj=this._targets[i].object;
            obj.userData.animateObject=undefined;
            this._targets.splice(i,1);
        }
    }

    /**
     * 步进
     * @param time {number} 以秒计的间隔时间
     */
    stepIn(time){
        for(let i=0;i<this._targets.length;i++){
            let ao=this._targets[i];
            ao._stepIn(time);
            ao._synchronize(this._path,this);
        }
    }

    //................................................private............................................

    _init(points,values){
        this._initVariable();
        this._initPath(points,values);
    }
    _initVariable(){
        this._path=null;
        this._targets=[];
        this._debug=null;
        this._tV0=new Vector3();
        this._tV1=new Vector3();
        this._tV2=new Vector3();
        this._tQ0=new Quaternion();
        this._tQ1=new Quaternion();
        this._tM0=new Matrix4();
    }
    _initPath(points,values){
        let ps=points||[new Vector3(0,0,0),new Vector3(1,0,0)];
        let closed=values.closed||false;
        let curveType=values.curveType||'catmullrom';
        let tension=values.tension||0;
        this._path=new CatmullRomCurve3(ps,closed,curveType,tension);
    }
}